package icasue.reflect.utils;

import icasue.reflect.handles.fields.FieldOF;
import icasue.reflect.handles.object.ObjectOF;
import icasue.reflect.handles.predicate.SureOF;

import java.util.Optional;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/1/7 下午2:02
 * @UpdateDate:
 * @Description:
 */
public class ProxyDigestUtil {

    /**
     * This method's function ins to handle methods as below:
     *  #exposeSpringEnhancerInst
     *  #exposeProxyStrongerEnhancerInst
     * @param cglibProxy
     * @param <T>
     * @return
     * @throws Throwable
     */
    public static <T> Optional<T> exposeOrigInstanceUnderCglib(T cglibProxy) throws Throwable{
        if(ObjectOF.isNull_.test(cglibProxy)) return Optional.ofNullable(null);
        String clsFullName = cglibProxy.getClass().getName();
        if(ObjectOF.equals_.test(Boolean.TRUE,clsFullName.contains("$$Enhancer")))
            if(ObjectOF.equals_.test(Boolean.TRUE,clsFullName.contains("$$EnhancerBySpringCGLIB")))
                return exposeSpringEnhancerInst(cglibProxy);
            else if(ObjectOF.equals_.test(Boolean.TRUE,clsFullName.contains("$$EnhancerByCGLIB")))
                try { return exposeProxyStrongerEnhancerInst(cglibProxy); }catch (Throwable e){
                    /*Here if throwing exception, means others third framework using CGLIB, we can't predicate inner store. return original inst.*/
                }
            else
                return Optional.ofNullable(cglibProxy);
        return Optional.ofNullable(cglibProxy);
    }


    /**
     * This method is to find suitable instance, which having the field your careful.
     * notice: Be careful to using this method, except for developer know cls metadata clearly.
     * @param cglibProxy
     * @param careFiled
     * @param <T>
     * @return
     * @throws Throwable
     */
    private static <T> Optional<T> exposeInstUnderCglibDeeply(T cglibProxy,String careFiled) throws Throwable{
        if(ObjectOF.isNull_.test(cglibProxy)) return Optional.ofNullable(null);
        try {
            for (T ref = cglibProxy; ref != null; ref = exposeOrigInstanceUnderCglib(ref).orElse(null))
                try {
                    Object careValue = FieldOF.get_.apply(FieldOF.findField_ofTp_fName_.apply(ref.getClass(), careFiled), ref);
                    if (ObjectOF.notNull_.test(careValue)) return Optional.ofNullable(ref); else continue;
                }catch (Throwable e){ /*Here occur exception is normally, course of proxy inst mayBe do't have this field.*/}
        }catch (Throwable e){ /*Occur exception on Digest proxy's process may be proxy native to third dependency. */ }
        return Optional.ofNullable(cglibProxy);
    }


    /**
     * This api attend to expose original inst under SpringCGLIB's enhancer.
     * @param cglibProxy
     * @param <T>
     * @return
     * @throws Throwable
     */
    private static <T> Optional<T> exposeSpringEnhancerInst(T cglibProxy) throws Throwable{
        T realInst = (T)ObjectOF.getNull_.get();
        Class<?> enhancerCglibCls = Class.forName(cglibProxy.getClass().getName());
        FieldOF.findField_ofTp_fName_.apply(enhancerCglibCls, "CGLIB$CALLBACK_1");
        Object cglib$CALLBACK_1_OUTER = FieldOF.get_.apply(FieldOF.findField_ofTp_fName_.apply(enhancerCglibCls, "CGLIB$CALLBACK_1"), cglibProxy);
        SureOF.notNull(cglib$CALLBACK_1_OUTER,"ProxyDigestUtil :: exposeSpringEnhancerInst :: cglib$CALLBACK_1_OUTER enhancer NULL Object.");
        Class<?> cglib$INTER = Class.forName(cglib$CALLBACK_1_OUTER.getClass().getName());
        realInst = (T) FieldOF.get_.apply(FieldOF.findField_ofTp_fName_.apply(cglib$INTER, "target"), cglib$CALLBACK_1_OUTER);
        return Optional.ofNullable(realInst);
    }

    /**
     * This api attend to expose original inst under CGLIB's enhancer.
     * notice, this method only deal with using LBS-PLUGIN-STRONGER-ADVICE's app,
     * or other components be registered by third dependency and is similar to StrongerAdvice on details, but actual poorly.
     * @param cglibProxy
     * @param <T>
     * @return
     * @throws Throwable
     */
    private static <T> Optional<T> exposeProxyStrongerEnhancerInst(T cglibProxy) throws Throwable{
        T realInst = (T)ObjectOF.getNull_.get();
        Class<?> enhancerCglibCls = Class.forName(cglibProxy.getClass().getName());
        Object cglib$CALLBACK_0_OUTER = FieldOF.get_.apply(FieldOF.findField_ofTp_fName_.apply(enhancerCglibCls, "CGLIB$CALLBACK_0"), cglibProxy);
        SureOF.notNull(cglib$CALLBACK_0_OUTER,"ProxyDigestUtil :: exposeProxyStrongerEnhancerInst :: cglib$CALLBACK_0_OUTER enhancer NULL Object.");
        Class<?> cglib$INTER = Class.forName(cglib$CALLBACK_0_OUTER.getClass().getName());
        Object cglib$INTER_AS_ProxyStronger = FieldOF.get_.apply(FieldOF.findField_ofTp_fName_.apply(cglib$INTER, "this$0"), cglib$CALLBACK_0_OUTER);
        SureOF.notNull(cglib$INTER_AS_ProxyStronger,"ProxyDigestUtil :: exposeProxyStrongerEnhancerInst :: cglib$INTER_AS_ProxyStronger enhancer NULL Object.");
        Class<?> proxyStrongerCls = Class.forName(cglib$INTER_AS_ProxyStronger.getClass().getName());
        realInst = (T)FieldOF.get_.apply(FieldOF.findField_ofTp_fName_.apply(proxyStrongerCls, "realInvokeObject"), cglib$INTER_AS_ProxyStronger);
        return Optional.ofNullable(realInst);
    }

}
